package org.iabako.server.serviceimpl.business;

import org.iabako.server.dao.JpaDAO;
import org.iabako.server.dao.business.ExpenseDAO;
import org.iabako.server.dao.business.QueryDefinitionDAO;
import org.iabako.server.tools.ServerTools;
import org.iabako.shared.dto.SuggestItemDTO;
import org.iabako.shared.entity.business.AbstractEntity;
import org.iabako.shared.entity.business.Expense;
import org.iabako.shared.entity.business.Installment;
import org.iabako.shared.entity.business.InstallmentsDefinition;
import org.iabako.shared.entity.enumeration.*;
import org.iabako.shared.entity.user.Enterprise;
import org.iabako.shared.exception.IabakoActionForbiddenException;
import org.iabako.shared.exception.IabakoUniqueConstraintException;
import org.iabako.shared.iservice.business.ExpenseService;
import org.iabako.shared.iservice.business.TrackingService;
import org.iabako.shared.querydefinition.SearchMotorQueryEnum;
import org.iabako.shared.querydefinition.columndefinition.ColumnDefinition;
import org.iabako.shared.querydefinition.columndefinition.ExpenseColumnEnum;
import org.iabako.shared.querydefinition.dto.QueryDefinitionDTO;
import org.iabako.shared.querydefinition.dto.QueryOperatorEnum;
import org.iabako.shared.querydefinition.dto.QueryResultDTO;
import org.iabako.shared.querydefinition.dto.QueryResultValue;
import org.iabako.shared.tools.GenericTools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by jose on 04/11/14.
 */
@org.springframework.stereotype.Service("expenseService")
public class ExpenseServiceImpl extends AbstractServiceImpl implements ExpenseService {

    @Autowired
    private ExpenseDAO expenseDAO;
    @Autowired
    private QueryDefinitionDAO queryDefinitionDAO;
    @Autowired
    private TrackingService trackingService;

    @Override
    protected JpaDAO getDAO() {
        return expenseDAO;
    }

    public QueryDefinitionDTO getSearchQueryDefinitionDTO(QueryDefinitionDTO queryDefinitionDTO) {
        queryDefinitionDTO.setSearchMotorQuery(SearchMotorQueryEnum.ExpenseSearch);
        queryDefinitionDTO.addCriteria(ExpenseColumnEnum.expenseEnterprise, QueryOperatorEnum.IN, (ArrayList) getSessionUser().getEnterprise().getVisibleEnterprisesFinancialData());
        queryDefinitionDAO.executeQueryDefinition(queryDefinitionDTO);

        queryDefinitionDTO.setCustomModifications(true);

        for (QueryResultDTO result : queryDefinitionDTO.getResults().values()) {
            QueryResultValue qrValue = new QueryResultValue();

            Date installmentDate = (Date) (result.getValues().get(ExpenseColumnEnum.paymentDate)).getValue();
            PaymentMethod paymentMethod = (PaymentMethod) (result.getValues().get(ExpenseColumnEnum.paymentMethod)).getValue();

            if (paymentMethod != null) {
                qrValue.setValue(ExpenseStatus._3_OK);
            } else if (installmentDate.compareTo(GenericTools.getDateDay()) > 0) {
                qrValue.setValue(ExpenseStatus._2_withFutureInstallments);
            } else {
                qrValue.setValue(ExpenseStatus._1_inDebt);
            }

            result.getValues().put(ExpenseColumnEnum.status, qrValue);
        }

        return queryDefinitionDTO;
    }

    public QueryDefinitionDTO setSearchCriteriaFromSuggest(QueryDefinitionDTO qd, Long hiddenSelectionCode, String suggestionValue) {
        if (!GenericTools.isEmpty(hiddenSelectionCode)) {
            qd.addCriteria(ExpenseColumnEnum.category, QueryOperatorEnum.EQUALS, suggestionValue);
            return qd;
        }
        return setSearchCriteriaFromSuggest(qd,
                suggestionValue,
                new ColumnDefinition[]{ExpenseColumnEnum.number, ExpenseColumnEnum.category});
    }

    public List<SuggestItemDTO> getSuggestions(String req) {
        List<SuggestItemDTO> suggestions = new ArrayList<SuggestItemDTO>(10);

        List<String> results = expenseDAO.getCategoriesBySuggestion(req);
        long index = 0;
        for (String result : results) {
            suggestions.add(new SuggestItemDTO(++index + "", result, null, null, null));
        }

        return suggestions;
    }

    public AbstractEntity getByHiddenSelectionCode(String hiddenSelectionCode) {
        return getById(Long.parseLong(hiddenSelectionCode));
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Expense save(Expense expense, List<Installment> customInstallmentsDefinition) throws IabakoActionForbiddenException, IabakoUniqueConstraintException {

        Enterprise enterprise = getEnterpriseFromSessionUser();

        if (enterprise != null && enterprise.isDemo()) {
            throw new IabakoActionForbiddenException("action_forbidden_for_demo_title", "action_forbidden_for_demo_text", true);
        }

        boolean modificationMode = expense.getId() != null;
        Expense expenseInDB = modificationMode ? expenseDAO.findById(expense.getId()) : null;
        boolean numberChanged = modificationMode && !expenseInDB.getNumber().equals(expense.getNumber());

        if (expense.getEnterprise() == null) {
            if (modificationMode && enterprise.getAllRelatedEnterprises().size() > 1) {
                //Do not change Enterprise!
                expense.setEnterprise(expenseInDB.getEnterprise());
            } else {
                expense.setEnterprise(enterprise);
            }
        }

        //Si creationMode or numberChanged and new number is not unique, we must throw UniqueConstraintException
        if ((!modificationMode || numberChanged) && !getDAO().isUniqueNumber(expense)) {
            String prefix = expense.getNumber().contains("-") ?
                    expense.getNumber().substring(0, expense.getNumber().lastIndexOf("-")) : expense.getNumber();
            throw new IabakoUniqueConstraintException(
                    messages.getLabel("validation_expense_unique_constraint_error_title"),
                    messages.getLabel("validation_expense_unique_constraint_error_text", prefix), true, true);
        }

        expense.setPaymentDate(GenericTools.getDateFromString(expense.getPaymentDateString()));

        boolean hasInstallments = expense.getExpenseType() == ExpenseType.several
                && expense.getInstallmentsDefinition() != null;

        setBusinessTrackInfo(expense);

        if (modificationMode) {
            //Modification mode
            if (expenseInDB.getPaymentMethod() == null && expense.getPaymentMethod() != null) {
                trackingService.addTrackingToUserSession(TrackingType.expensePayed, expense);
            } else {
                trackingService.addTrackingToUserSession(TrackingType.expenseModify, expense);
            }
            return (Expense) super.save(expense);
        }

        //Creation mode :
        if (!hasInstallments) {
            trackingService.addTrackingToUserSession(TrackingType.expenseNew, expense);
            return (Expense) super.save(expense);
        }

        //Creation of Expense with installments
        List<Installment> pastInstallments = new ArrayList<Installment>();
        List<Installment> futureInstallments = new ArrayList<Installment>();
        List<Installment> allInstallments = new ArrayList<Installment>();

        expense.getInstallmentsDefinition().setInstallments(customInstallmentsDefinition);

        expense.getInstallmentsDefinition().calculatePastFutureInstallments(new ServerTools(), null, pastInstallments, futureInstallments);
        allInstallments.addAll(pastInstallments);
        allInstallments.addAll(futureInstallments);

        if (expense.getInstallmentsDefinition().getInstallmentsType() == InstallmentsType.undefined) {

            Date lastInstallmentDate = pastInstallments.isEmpty() ?
                    expense.getInstallmentsDefinition().getInstallmentsPeriodicalStartDate()
                    : pastInstallments.get(pastInstallments.size() - 1).getDate();

            Date nextUndefinedInstallment = expense.getInstallmentsDefinition().addNToDate(new ServerTools(),
                    lastInstallmentDate,
                    expense.getInstallmentsDefinition().getInstallmentsPeriodicalFrequency(),
                    expense.getInstallmentsDefinition().getInstallmentsPeriodicalFrequencyN());

            expense.getInstallmentsDefinition().setNextUndefinedInstallment(nextUndefinedInstallment);
        }

        if (allInstallments.isEmpty() && expense.getInstallmentsDefinition().getInstallmentsType() == InstallmentsType.undefined) {

            //Undefined future installments
            //We create at least the first future expense
            Expense newExpense = getNewExpense(1, expense,
                    expense.getInstallmentsDefinition().getInstallmentsPeriodicalStartDate(),
                    expense.getInstallmentsDefinition().getInstallmentsPeriodicalPrice());

            setBusinessTrackInfo(newExpense);
            trackingService.addTrackingToUserSession(TrackingType.expenseNew, newExpense);

            super.save(newExpense);

        } else {
            //Defined past and future installments
            int index = 1;
            for (Installment installment : allInstallments) {
                Expense newExpense = getNewExpense(index++, expense, installment.getDate(), installment.getAmount());

                setBusinessTrackInfo(newExpense);
                trackingService.addTrackingToUserSession(TrackingType.expenseNew, newExpense);

                super.save(newExpense);
            }
        }

        //Returned object is never used
        return expense;
    }

    public Expense getNewExpense(int index, Expense expenseReference, Date newDate, Double amount) {
        Expense newExpense = new Expense();
        String prefix = expenseReference.getNumber();
        if (prefix.contains("-")) {
            prefix = prefix.substring(0, prefix.indexOf("-"));
        }
        newExpense.setNumber(prefix + "-" + index);
        newExpense.setCategory(expenseReference.getCategory());
        newExpense.setDescription(expenseReference.getDescription());
        newExpense.setExpenseType(expenseReference.getExpenseType());
        newExpense.setInstallmentsDefinition(expenseReference.getInstallmentsDefinition());
        newExpense.setPaymentDate(newDate);
        newExpense.setPaymentValue(amount);

        newExpense.setEnterprise(expenseReference.getEnterprise());

        return newExpense;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Expense stopUndefinedExpensesCreation(Expense expense) throws IabakoActionForbiddenException {
        InstallmentsDefinition iDef = expense.getInstallmentsDefinition();
        Date lastUndefinedInstallment = iDef.addNToDate(new ServerTools(), iDef.getNextUndefinedInstallment(), iDef.getInstallmentsPeriodicalFrequency(), iDef.getInstallmentsPeriodicalFrequencyN() * -1);

        expense.getInstallmentsDefinition().setNextUndefinedInstallment(null);
        expense.getInstallmentsDefinition().setStopUndefinedInstallments(new Date());
        if (lastUndefinedInstallment.compareTo(iDef.getInstallmentsPeriodicalStartDate()) >= 0) {
            expense.getInstallmentsDefinition().setLastUndefinedInstallment(lastUndefinedInstallment);
        }

        return (Expense) save(expense);
    }

    public Double getTotal(QueryDefinitionDTO queryDefinitionDTO) {
        queryDefinitionDTO.getSelectColumns().clear();
        queryDefinitionDTO.setCustomQuery(true);
        queryDefinitionDTO.addCustomColumn(ExpenseColumnEnum.expenseTotal);

        queryDefinitionDAO.executeQueryDefinition(queryDefinitionDTO);

        return (Double) new ArrayList<QueryResultDTO>(queryDefinitionDTO.getResults().values()).get(0).getValues().get(ExpenseColumnEnum.expenseTotal).getValue();
    }

    public List<String> getAllCustomCategories() {
        return expenseDAO.getAllCustomCategories();
    }

    public Double getLastTax() {
        return expenseDAO.getLastTax();
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Boolean remove(Long id) {
        expenseDAO.deleteAttachedObjects(id);
        trackingService.addTrackingToUserSession(TrackingType.expenseDelete, (Expense) getById(id));
        return super.remove(id);
    }
}
